package service

import (
	"cloud-export-iotkit-mqtt/pkg/config"
	"cloud-export-iotkit-mqtt/pkg/driver"
	"cloud-export-iotkit-mqtt/pkg/driver/mqtt"
	"cloud-export-iotkit-mqtt/pkg/iita/gateway"
	cloudTransforms "cloud-export-iotkit-mqtt/pkg/transforms"
	"encoding/json"
	"fmt"
	"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg"
	"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/interfaces"
	"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"

	"os"
)

type OpenIitaService struct {
	lc             logger.LoggingClient
	serviceConfig  *config.ServiceConfig
	service        interfaces.ApplicationService
	gatewayService gateway.GatewayImpl

	ServiceCh driver.ProtocolServiceChanel

	// todo: 重构
	msgChan    chan []byte
	maxChanLen int
}

func (iitaService *OpenIitaService) Initialize(serviceKey string) {
	// 1) First thing to do is to create an new instance of an EdgeX Application Service.
	service, ok := pkg.NewAppService(serviceKey)
	if !ok {
		os.Exit(-1)
	}

	iitaService.service = service
	iitaService.maxChanLen = 100
	iitaService.msgChan = make(chan []byte, iitaService.maxChanLen)

	// Leverage the built in logging service in EdgeX
	lc := service.LoggingClient()

	iitaService.lc = lc

	// 2) Configure pipeline functions that require more complex configuration. Here we
	//    use the advanced structure custom configuration
	config := config.ServiceConfig{}
	if err := service.LoadCustomConfig(&config, "MQTTConfig"); err != nil {
		lc.Errorf("LoadCustomConfig returned error: %s", err.Error())
		os.Exit(-1)
	}

	if err := config.Validate(); err != nil {
		lc.Errorf("Custom Config failed validation: %s", err.Error())
		os.Exit(-1)
	}
	mqttClient := mqtt.NewMqttClient(lc, service)

	iitaService.ServiceCh = mqttClient
	// todo
	gatewayConfig := gateway.GatewayConfig{}
	gatewayConfig.DeviceName = config.MqttClientConfig.DeviceName
	gatewayConfig.ProductKey = config.MqttClientConfig.ProductKey
	gatewayConfig.Model = config.MqttClientConfig.Model

	g := gateway.Gateway{GatewayConfig: gatewayConfig}

	iitaService.gatewayService = gateway.GatewayImpl{
		ChannelConfig: config.MqttClientConfig, Gateway: g, Service: service, ServiceCh: mqttClient}

	//// 3) This is our pipeline configuration, the collection of functions to
	//// execute every time an event is triggered.
	if err := service.AddFunctionsPipelineForTopics("edgex/events/#", []string{"edgex/events/#"},
		cloudTransforms.NewConversion().PrintToConsole,
		cloudTransforms.NewConversion().TransformToCloudFormat,
		iitaService.Send2Queue,
	); err != nil {
		lc.Errorf("SetFunctionsPipeline returned error: %s", err.Error())
		os.Exit(-1)
	}

}

func (iita *OpenIitaService) Run() {

	go iita.send2Mqtt()
	defer iita.ServiceCh.Destroy()
	iita.gatewayService.Initialize()
	iita.gatewayService.Run()

	if err := iita.service.MakeItRun(); err != nil {
		iita.lc.Errorf("MakeItRun returned error: %s", err.Error())
		os.Exit(-1)
	}
	// Do any required cleanup here
}

func (c *OpenIitaService) send2Mqtt() {

	for {
		select {

		case data := <-c.msgChan:
			c.lc.Infof("get msg from queue:%s", data)

			var resMap map[string]interface{}

			json.Unmarshal(data, &resMap)

			deviceName := resMap["deviceName"].(string)
			device, exist := c.gatewayService.GetSubDeviceById(deviceName)
			if !exist {
				// TOTO: 添加设备

				c.lc.Infof("device does not exist: %s", deviceName)
				continue
			}

			deviceReportDataTopic := fmt.Sprintf("/sys/%s/%s/s/event/property/post", device.ProductKey, deviceName)

			token := c.ServiceCh.Publish(deviceReportDataTopic, 1, data, -1)

			c.lc.Infof("send2mqtt:%s, %v", deviceReportDataTopic, token.Error())
		}

	}

}

func (client *OpenIitaService) Send2Queue(ctx interfaces.AppFunctionContext, data interface{}) (bool, interface{}) {
	if data == nil {
		return false, fmt.Errorf("function MqttSend in pipeline '%s': No Data Received", ctx.PipelineId())
	}

	// 发送到队列里,由其他线程发送出去
	nowLen := len(client.msgChan)
	if nowLen >= client.maxChanLen {
		return false, fmt.Errorf("function MqttSend in pipeline '%s':  Max Chan Len", ctx.PipelineId())
	}

	client.msgChan <- data.([]byte)

	return true, nil
}
